{
  config,
  lib,
  pkgs,
  ...
}:
let
  inherit (lib)
    literalExpression
    mkEnableOption
    mkIf
    mkOption
    optionalString
    types
    mkMerge
    mkOrder
    ;

  cfg = config.programs.kitty;

  settingsValueType =
    with types;
    oneOf [
      str
      bool
      int
      float
    ];

  optionalPackage = opt: lib.optional (opt != null && opt.package != null) opt.package;

  toKittyConfig = lib.generators.toKeyValue {
    mkKeyValue =
      key: value:
      let
        value' = (if lib.isBool value then lib.hm.booleans.yesNo else toString) value;
      in
      "${key} ${value'}";
  };

  toKittyKeybindings = lib.generators.toKeyValue {
    mkKeyValue = key: command: "map ${key} ${command}";
  };

  toKittyMouseBindings = lib.generators.toKeyValue {
    mkKeyValue = key: command: "mouse_map ${key} ${command}";
  };

  toKittyActionAliases = lib.generators.toKeyValue {
    mkKeyValue = alias_name: action: "action_alias ${alias_name} ${action}";
  };

  toKittyEnv = lib.generators.toKeyValue {
    mkKeyValue = name: value: "env ${name}=${value}";
  };

  shellIntegrationInit = {
    bash = ''
      if test -n "$KITTY_INSTALLATION_DIR"; then
        export KITTY_SHELL_INTEGRATION="${cfg.shellIntegration.mode}"
        source "$KITTY_INSTALLATION_DIR/shell-integration/bash/kitty.bash"
      fi
    '';
    fish = ''
      if set -q KITTY_INSTALLATION_DIR
        set --global KITTY_SHELL_INTEGRATION "${cfg.shellIntegration.mode}"
        source "$KITTY_INSTALLATION_DIR/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish"
        set --prepend fish_complete_path "$KITTY_INSTALLATION_DIR/shell-integration/fish/vendor_completions.d"
      end
    '';
    zsh = ''
      if test -n "$KITTY_INSTALLATION_DIR"; then
        export KITTY_SHELL_INTEGRATION="${cfg.shellIntegration.mode}"
        autoload -Uz -- "$KITTY_INSTALLATION_DIR"/shell-integration/zsh/kitty-integration
        kitty-integration
        unfunction kitty-integration
      fi
    '';
  };

  mkShellIntegrationOption =
    option:
    option
    // {
      default =
        (cfg.shellIntegration.mode != null)
        && !(lib.elem "disabled" (lib.splitString " " cfg.shellIntegration.mode));
      defaultText = literalExpression ''
        (cfg.shellIntegration.mode != null)
        && !(elem "disabled" (splitString " " config.programs.kitty.shellIntegration.mode))
      '';
    };
in
{
  imports = [
    (lib.mkChangedOptionModule
      [ "programs" "kitty" "theme" ]
      [
        "programs"
        "kitty"
        "themeFile"
      ]
      (
        config:
        let
          value = lib.getAttrFromPath [ "programs" "kitty" "theme" ] config;
        in
        if value != null then
          (
            let
              matching = lib.filter (x: x.name == value) (
                lib.importJSON "${pkgs.kitty-themes}/share/kitty-themes/themes.json"
              );
            in
            lib.throwIf (lib.length matching == 0) "kitty-themes does not contain a theme named ${value}"
              lib.strings.removeSuffix
              ".conf"
              (lib.strings.removePrefix "themes/" (lib.head matching).file)
          )
        else
          null
      )
    )
  ];

  meta.maintainers = with lib.maintainers; [ khaneliman ];

  options.programs.kitty = {
    enable = mkEnableOption "Kitty terminal emulator";

    package = lib.mkPackageOption pkgs "kitty" { nullable = true; };

    darwinLaunchOptions = mkOption {
      type = types.nullOr (types.listOf types.str);
      default = null;
      description = "Command-line options to use when launched by Mac OS GUI";
      example = literalExpression ''
        [
          "--single-instance"
          "--directory=/tmp/my-dir"
          "--listen-on=unix:/tmp/my-socket"
        ]
      '';
    };

    settings = mkOption {
      type = types.attrsOf settingsValueType;
      default = { };
      example = literalExpression ''
        {
          scrollback_lines = 10000;
          enable_audio_bell = false;
          update_check_interval = 0;
        }
      '';
      description = ''
        Configuration written to
        {file}`$XDG_CONFIG_HOME/kitty/kitty.conf`. See
        <https://sw.kovidgoyal.net/kitty/conf.html>
        for the documentation.
      '';
    };

    themeFile = mkOption {
      type = types.nullOr types.str;
      default = null;
      description = ''
        Apply a Kitty color theme. This option takes the file name of a theme
        in `kitty-themes`, without the `.conf` suffix. See
        <https://github.com/kovidgoyal/kitty-themes/tree/master/themes> for a
        list of themes.
      '';
      example = "SpaceGray_Eighties";
    };

    font = mkOption {
      type = types.nullOr lib.hm.types.fontType;
      default = null;
      description = "The font to use.";
    };

    actionAliases = mkOption {
      type = types.attrsOf types.str;
      default = { };
      description = "Define action aliases.";
      example = literalExpression ''
        {
          "launch_tab" = "launch --cwd=current --type=tab";
          "launch_window" = "launch --cwd=current --type=os-window";
        }
      '';
    };

    keybindings = mkOption {
      type = types.attrsOf types.str;
      default = { };
      description = "Mapping of keybindings to actions.";
      example = literalExpression ''
        {
          "ctrl+c" = "copy_or_interrupt";
          "ctrl+f>2" = "set_font_size 20";
        }
      '';
    };

    mouseBindings = mkOption {
      type = types.attrsOf types.str;
      default = { };
      description = "Mapping of mouse bindings to actions.";
      example = literalExpression ''
        {
          "ctrl+left click" = "ungrabbed mouse_handle_click selection link prompt";
          "left click" = "ungrabbed no-op";
        };
      '';
    };

    environment = mkOption {
      type = types.attrsOf types.str;
      default = { };
      description = "Environment variables to set or override.";
      example = literalExpression ''
        {
          "LS_COLORS" = "1";
        }
      '';
    };

    shellIntegration = {
      mode = mkOption {
        type = types.nullOr types.str;
        default = "no-rc";
        example = "no-cursor";
        apply = lib.mapNullable (
          o:
          let
            modes = lib.splitString " " o;
            filtered = lib.filter (m: m != "no-rc") modes;
          in
          lib.concatStringsSep " " (
            lib.concatLists [
              [ "no-rc" ]
              filtered
            ]
          )
        );
        description = ''
          Set the mode of the shell integration. This accepts the same options
          as the `shell_integration` option of Kitty. Note that
          `no-rc` is always implied, unless this set to `null`. See
          <https://sw.kovidgoyal.net/kitty/shell-integration>
          for more details.
        '';
      };

      enableBashIntegration = mkShellIntegrationOption (
        lib.hm.shell.mkBashIntegrationOption { inherit config; }
      );

      enableFishIntegration = mkShellIntegrationOption (
        lib.hm.shell.mkFishIntegrationOption { inherit config; }
      );

      enableZshIntegration = mkShellIntegrationOption (
        lib.hm.shell.mkZshIntegrationOption { inherit config; }
      );
    };

    enableGitIntegration = lib.mkEnableOption "git integration";

    extraConfig = mkOption {
      default = "";
      type = types.lines;
      description = "Additional configuration to add to kitty.conf.";
    };

    quickAccessTerminalConfig = mkOption {
      type = types.attrsOf settingsValueType;
      default = { };
      example = literalExpression ''
        {
          start_as_hidden = false;
          hide_on_focus_loss = false;
          background_opacity = 0.85;
        }
      '';
      description = ''
        Configuration written to
        {file}`$XDG_CONFIG_HOME/kitty/quick-access-terminal.conf`. See
        <https://sw.kovidgoyal.net/kitty/kittens/quick-access-terminal/>
        for the documentation.
      '';
    };
  };

  config = mkIf cfg.enable {
    assertions = [
      {
        assertion =
          !(
            cfg.shellIntegration.mode == null
            && (
              cfg.shellIntegration.enableBashIntegration
              || cfg.shellIntegration.enableFishIntegration
              || cfg.shellIntegration.enableZshIntegration
            )
          );
        message = "Cannot enable shell integration when `programs.kitty.shellIntegration.mode` is `null`";
      }
    ];

    home.packages = (optionalPackage cfg) ++ (optionalPackage cfg.font);

    programs.kitty.extraConfig = mkMerge [
      (mkIf (cfg.font != null) (
        mkOrder 510 ''
          font_family ${cfg.font.name}
          ${optionalString (cfg.font.size != null) "font_size ${toString cfg.font.size}"}
        ''
      ))
      (mkIf (cfg.themeFile != null) (
        mkOrder 520 ''
          include ${pkgs.kitty-themes}/share/kitty-themes/themes/${cfg.themeFile}.conf
        ''
      ))
      (mkIf (cfg.shellIntegration.mode != null) (
        mkOrder 530 ''
          # Shell integration is sourced and configured manually
          shell_integration ${cfg.shellIntegration.mode}
        ''
      ))
      (mkOrder 540 (toKittyConfig cfg.settings))
      (mkOrder 550 (toKittyActionAliases cfg.actionAliases))
      (mkOrder 560 (toKittyKeybindings cfg.keybindings))
      (mkOrder 570 (toKittyMouseBindings cfg.mouseBindings))
      (mkOrder 580 (toKittyEnv cfg.environment))
    ];

    xdg.configFile."kitty/kitty.conf" = {
      text = ''
        # Generated by Home Manager.
        # See https://sw.kovidgoyal.net/kitty/conf.html
        ${cfg.extraConfig}
      '';
    }
    // lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
      onChange = ''
        ${pkgs.procps}/bin/pkill -USR1 -u $USER kitty || true
      '';
    }
    // lib.optionalAttrs pkgs.stdenv.hostPlatform.isDarwin {
      onChange = ''
        /usr/bin/pkill -USR1 -u $USER kitty || true
      '';
    };

    xdg.configFile."kitty/quick-access-terminal.conf" = mkIf (cfg.quickAccessTerminalConfig != { }) {
      text = ''
        # Generated by Home Manager.
        # See https://sw.kovidgoyal.net/kitty/kittens/quick-access-terminal/
        ${toKittyConfig cfg.quickAccessTerminalConfig}
      '';
    };

    home.activation.checkKittyTheme = mkIf (cfg.themeFile != null) (
      let
        themePath = "${pkgs.kitty-themes}/share/kitty-themes/themes/${cfg.themeFile}.conf";
      in
      lib.hm.dag.entryBefore [ "writeBoundary" ] ''
        if [[ ! -f "${themePath}" ]]; then
          errorEcho "kitty-themes does not contain the theme file ${themePath}!"
          exit 1
        fi
      ''
    );

    xdg.configFile."kitty/macos-launch-services-cmdline" =
      mkIf (cfg.darwinLaunchOptions != null && pkgs.stdenv.hostPlatform.isDarwin)
        {
          text = lib.concatStringsSep " " cfg.darwinLaunchOptions;
        };

    programs.bash.initExtra = mkIf cfg.shellIntegration.enableBashIntegration shellIntegrationInit.bash;

    programs.fish.interactiveShellInit = mkIf cfg.shellIntegration.enableFishIntegration shellIntegrationInit.fish;

    programs.zsh.initContent = mkIf cfg.shellIntegration.enableZshIntegration shellIntegrationInit.zsh;

    programs.git.iniContent = lib.mkIf cfg.enableGitIntegration {
      diff = {
        tool = lib.mkDefault "kitty";
        guitool = lib.mkDefault "kitty.gui";
      };
      difftool = {
        prompt = lib.mkDefault false;
        trustExistCode = lib.mkDefault true;
        kitty = {
          cmd = "kitten diff $LOCAL $REMOTE";
        };
        "kitty.gui" = {
          cmd = "kitten diff $LOCAL $REMOTE";
        };
      };
    };
  };
}
